<?php
/*
 * Copyright (c) 2008 Sergey Bondari, bondari@bondari.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Mosaic\System;

/**
 * Collection of system Packages/Components/Resources
 * 
 * In fact, initialized instance of this class represents a single node
 * processed packages manifest.
 *
 * @package System
 * @subpackage Node
 */
class InfoCollection {
	/**
	 * Map of package information objects
	 */
	private $packages;

	/**
	 * Map of component information obejcts
	 */
	private $components;

	/**
	 * Map of resources information obejcts
	 */
	private $resources;
	

	/**
	 * Deferred resource requirement statements
	 */
	private $deferredResourceStatements;

	/**
	 * RDF parse of all loaded manifests
	 * @access private
	 * @var RDF
	 */
	public $rdf;


	function __construct() {
		$this->rdf = new \Mosaic\LIB\RDF();
	}


	/**
	 * Adds a new package information into the collection
	 * @param $package PackageInfo
	 * @return void
	 */
	public function addPackageInfo(PackageInfo $package) {
		trigger_error('Adding information about package '.$package->systemID);
		$this->packages[$package->systemID] = $package;
	}

	/**
	 * Adds a new component information into the collection
	 * @param $packageSystemID string component's package id
	 * @param $component ComponentInfo 
	 * @return void
	 */
	public function addComponentInfo($packageSystemID, ComponentInfo $component) {
		trigger_error('Adding information about component '.$component->systemID);

		if (isset($this->components[$component->systemID])) {
			throw new RuntimeException('Ambigious component ['.$component->systemID.']');
		}

		if (!isset($this->packages[$packageSystemID])) {
			throw new RuntimeException('Package ['. $packageSystemID.'] is not in collection');
		}

		/**
		 * Bind component info with package info
		 */
		$this->packages[$packageSystemID]->addComponent($component);

		
		$this->components[$component->systemID] = $component;
	}


	/**
	 * Adds a new resource information instance into the collection
	 *
	 * @param $resource Resource
	 * @param $componentID string Component that provides the resource
	 * @return void
	 */
	public function addResourceInfo(Resource $resource, $componentID) {
		trigger_error('Adding information about resource '.$resource->key());

		if ($componentID) if (!isset($this->components[$componentID])) {
			throw new RuntimeException('Component ['.$componentID.'] is not in collection.');
		}

		if (!isset($this->resources[$resource->key()])) { /* Resource is new for the system */
			$this->resources[$resource->key()] = $resource;
		} else { /* Resource is already in the system. */
			$resource = $this->resources[$resource->key()];
		}

		# Add another providing component information
		if ($componentID) $resource->addProvidingComponent($componentID, $this->components[$componentID]);

		if (isset($this->deferredResourceStatements[$resource->key()])) {
			foreach ($this->deferredResourceStatements[$resource->key()] as $componentID) {
				$this->addResourceRequiredStatement($resource->key(), $componentID);
			}

			unset($this->deferedResourceStatements[$resource->key()]);

		}
	}


	/**
	 * Adds a resource requirement statement.
	 *
	 * @param $resourceKey string 
	 * @param $componentID string Component ID that requires this resource
	 */
	public function addResourceRequiredStatement($resourceKey, $componentID) {
		if (!isset($this->components[$componentID])) {
			throw new RuntimeException('Component ['.$componentID.'] is not in collection.');
		}

		if (!isset($this->resources[$resourceKey])) {
			$this->deferredResourceStatements[$resourceKey]['req'][] = $componentID;
			return;
		} else $resource = $this->resources[$resourceKey];


		$resource->addRequiringComponent($componentID, $this->components[$componentID]);
		$this->components[$componentID]->requiredResources[$resource->key()] = $resource;
	}






	function findComponentInfoById($systemID) {
		if (isset($this->components[$systemID])) return $this->components[$systemID]; else throw new RuntimeException(sprintf('Component [%s] is not in collection'), $systemID);
	}

	public function findResourceByKey($key) {
		if (isset($this->resources[$key])) {
			return $this->resources[$key]; 
		} else {
			trigger_error(sprintf('Missed resource by [%s] key', $key), E_USER_WARNING);
		}
	}

	public function findResourceByType($type) {
		$ret = array();
		foreach ($this->resources as $resource) {
			if ($resource->getType() == $type) {
				$ret[] = $resource;
			}
		}

		return $ret;
	}

	function findProvidingComponentInfo(Resource $resource) {
		
	}

	public function getManifest() {
		return $this->rdf->saveToString();

	}


	/**
	 * Load from XML
	 */
	public function loadManifestFromXML($manifest, $pathToManifest = '.') {
		$rdf = new \Mosaic\LIB\RDF();
		$rdf->loadXML($manifest);
		$this->rdf->unite($rdf);

		foreach ($rdf->find(new \Mosaic\LIB\RDFResource('mosaic:packages'), new \Mosaic\LIB\RDFResource(NS_RDF.'_1'), false) as $package) {
			/* 
			 * Get package system ID
			 */
			$systemID = (string) $package->getObject();
			$packageData = array();
			

			/*
			 * Read DC package data and components root node
			 */
			unset($componentsNode);
	
			foreach ($rdf->find(new \Mosaic\LIB\RDFResource($systemID), false, false) as $stmt) {
				if (preg_match('?'.NS_DC.'(.+)?', (string) $stmt->getPredicate(), $regs)) {
					$packageData[$regs[1]] = (string) $stmt->getObject();
				}

				if ((string)$stmt->getPredicate() == NS_MC.'components') {
					$componentsNode = $stmt->getObject();
				}
			}

			/*
			 * Add package into the local packages collection
			 */
			$packageInfo = new PackageInfo($systemID, $pathToManifest, $packageData);
			$this->addPackageInfo($packageInfo);

			if (!isset($componentsNode)) throw new RuntimeException('Could not find components declaration');
			/*
			 * Search the components
			 */
			$componentNodes = array();
			foreach ($rdf->find($componentsNode, false , false) as $stmt) {
				if (preg_match('?^'.NS_RDF.'_\d+'.'$?', (string) $stmt->getPredicate())) {
					$componentNodes[] = $stmt->getObject();
				}
			}

			/*
			 * Load information about each component
			 */
			foreach ($componentNodes as $componentNode) {
				$componentData = array();
				$providesNode = false;
				$dependsNode = false;

				/*
				 * Get component information
				 */
				foreach ($rdf->find($componentNode, false , false) as $stmt) {
					if (preg_match('?'.NS_DC.'(.+)?', (string) $stmt->getPredicate(), $regs)) { // any DC NS property (title, author ...)
						$componentData[$regs[1]] = (string) $stmt->getObject();
					} else if ((string)$stmt->getPredicate() == NS_MC.'provides') {
						$providesNode = $stmt->getObject();
					} else if ((string)$stmt->getPredicate() == NS_MC.'depends') {
						$dependsNode = $stmt->getObject();
					} else if (preg_match('?'.NS_MC.'(.+)?', (string) $stmt->getPredicate(), $regs)) { // any Mosaic NS property (class, component-type ...)
						$componentData[$regs[1]] = (string) $stmt->getObject();
					}

				}


				/*
				 * Add component into the package
				 */
				$componentInfo = new ComponentInfo((string) $componentNode, $componentData['class'], $componentData);
				$this->addComponentInfo($packageInfo->systemID, $componentInfo);

				/*
				 * Get provided/required resources
				 */
				foreach (array($providesNode, $dependsNode) as $node) {
					if ($node && !$node instanceof \Literal) {
						foreach ($rdf->find($node, false , false, false) as $stmt) {
							if (preg_match('?^'.NS_RDF.'_\d+'.'$?', (string) $stmt->getPredicate())) {
								$resourceData = array();
								foreach ($rdf->find($stmt->getObject(), false, false) as $stmt1) {

									if ((string) $stmt1->getPredicate() == NS_RDF.'type') {
										$resourceData['type'] = (string) $stmt1->getObject();
									}

									if (preg_match('?^'.NS_MC.'(.+)$?', (string)$stmt1->getPredicate(), $regs)) {
										if ($regs[1] == 'filename' || $regs[1] == 'stub' || $regs[1] == 'skeleton') {
											$resourceData[$regs[1]] = (string) $pathToManifest.'/'.$stmt1->getObject();
										} else {
											$resourceData[$regs[1]] = (string) $stmt1->getObject();
										}
									}
								}

								if ($node == $providesNode) {
									$this->addResourceInfo(ResourceInfo::create($resourceData['type'], $resourceData), $componentInfo->systemID);
								} else if ($node == $dependsNode) {
									if ($resourceData['type'] == 'mosaic:resource:include') $this->addResourceInfo(ResourceInfo::create($resourceData['type'], $resourceData), NULL);
									$this->addResourceRequiredStatement(ResourceInfo::create($resourceData['type'], $resourceData)->key(), $componentInfo->systemID);
								}
							}
						}
					}
				}


			}
		}




	}


	/**
	 * Load from file
	 */
	public function loadManifest($manifest) {
		$this->loadManifestFromXML(file_get_contents($manifest), dirname($manifest));
	}


}



/**
 * Package information container.
 *
 * This container is used by the component broker in order to store 
 * the information about the package during the manifest parsing process.
 */
class PackageInfo {
	/**
	 * Package system ID
	 * @var string
	 */
	public $systemID;

	/**
	 * Generic package metadata
	 * @var array
	 */
	public $data;

	/**
	 * Path to the package files
	 * @var string
	 */
	public $path;
	

	/**
	 * Information about components the package provides (@see ComponentInfo)
	 * @var array Array of ComponentInfo
	 */
	public $components;

	/**
	 * Container constructor
	 * @param string Package ID
	 * @param string Path to package files
	 * @param array Generic metadata
	 */
	function __construct($systemID, $path, $data) {
		$this->systemID = $systemID;
		$this->path = $path;
		$this->data = $data;
		
	}


	/**
	 * Adds component information
	 * @param ComponentInfo
	 */
	function addComponent($component) {
		$this->components[$component->systemID] = $component;
		$component->package = $this;

	}
}

/**
 * Component information container.
 *
 * This container is used by the component broker in order to store 
 * the information about the component during the manifest parsing process.
 */
class ComponentInfo {
	/**
	 * Reference to the package information
	 * @var PackageInfo
	 */
	public $package;

	/**
	 * Compoent system ID
	 * @var string
	 */
	public $systemID;

	/**
	 * Generic component metadata
	 * @var array
	 */
	public $data;

	/**
	 * Component class name
	 * @var string
	 */
	public $class;

	/**
	 * Component type
	 * @var STATEFUL|STATELESS
	 */
	public $componentType;

	/**
	 * Array of references to required resource information
	 * @var array
	 */
	public $requiredResources;

	/**
	 * Array of references to provided resource information
	 * @var STATEFUL|STATELESS
	 */
	public $providedResources;



	/**
	 * Container constructor
	 * @param string Component ID
	 * @param string Name of the component class
	 * @param array Generic metadata
	 */
	function __construct($systemID, $class, $data) {
		$this->systemID = $systemID;
		$this->data = $data;
		$this->class = $class;

		if (!isset($this->data['component-type'])) $this->data['component-type'] = 'stateless';

		if ($this->data['component-type'] == 'stateful') {
			$this->componentType = STATEFUL;
		} else {
			$this->componentType = STATELESS;
		}
	}

}


